#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "constantes.h"
#include "Lab3.h"
#include "envel.h"

/* struct Registro e usado na parte de criacao de arquivos menores de chaves ordenadas */
typedef struct Registro *VetorRegistros;
typedef struct Registro {
  char* info;
  int end_fisico;
} Registro;

/*struct Arquivo e usado para criar uma lista ligada das chaves a serem comparadas e ordenadas*/
typedef struct Arquivo *ListaArq;
typedef struct Arquivo {
	union {
		char* chave;
	    int chaveRa;
	 } tipo;
	int indice;
	int i;
	ListaArq proximo;
} Arquivo;

typedef FILE** inicio;


void verificaErros(int argumentos, char *entrada, char *campo, char* tamanhoMemoria, int *memoria)
{
	FILE *f;
	int i = 0;
	char c, nome[5], ra[3];

	if(argumentos < 5) //caso faltem argumentos para execucao do programa
	{
		fprintf(stderr, "Numero de argumentos insuficiente!\n");
		exit(EXIT_FAILURE);
	}

	f = fopen(entrada, "r");
	if (!arqExistente(f)) //verifica existencia do arquivo de registros de form. fixo
	{
		fprintf(stderr, "Arquivo de entrada inexistente!\n");
		exit(EXIT_FAILURE);
	}
	fclose(f);

	f = fopen("configLab3.txt", "r");

	//copia nome do campo para string
	do
	{
		ra[i] = getc(f);
		i++;
	} while (ra[i-1] != CARACTERSEPARADOR);

	ra[i-1] = FINALSTRING;
	i = 0;

	c = getc(f);
	while(c != QUEBRALINHA)
		c = getc(f);

	//copia nome do campo para string
	do
	{
		nome[i] = getc(f);
		i++;
	} while (nome[i-1] != CARACTERSEPARADOR);
	nome[i-1] = FINALSTRING;

	if (strcmp(campo, ra)) //confere se campo passado para ordenacao e valido
	{
		if (strcmp(campo, nome))
		{
			fprintf(stderr, "Nome de campo invalido!\n");
			exit(EXIT_FAILURE);
		}
	}

	fclose(f);

	if (!strcmp(tamanhoMemoria, "0")) //checa se foi passado memoria nula
	{
		fprintf(stderr, "Tamanho de memoria nulo!\n");
		exit(EXIT_FAILURE);
	}
	else
	{
		/*carrega o tamanho de memoria fornecido em um "int"*/
		for (i = 0; i < strlen(tamanhoMemoria); i++)
		{
			*memoria = *memoria * 10;
			*memoria = *memoria + ((int)tamanhoMemoria[i] - CONVERSAOINTEIRO);
		}

	}

}

void carregaConfiguracoes(int* comecoCampo, int* tamanhoCampo, char* campo)
{
	FILE *config;
	char c;

	config = fopen("configLab3.txt", "r");
	//abre arquivo de configuracoes

	if (!strcmp(campo, "NOME"))
	{
		c=CARACTERSEPARADOR;
		while (c != QUEBRALINHA)
			c = getc(config);
	}
		/* a partir daqui, carrega-se o tamanho do campo escolhido e seu comeco
		 * em cada registro nas variaveis que estao na main */
		 while(c != CARACTERSEPARADOR)
			 c = getc(config);

		 c = getc(config);
		 while (c != CARACTERSEPARADOR)
		 {
			 *comecoCampo = (*comecoCampo)*10;
			 *comecoCampo = *comecoCampo + (int)c - CONVERSAOINTEIRO;
			 c = getc(config);
		 }
		 c = getc(config);
		 while (c != QUEBRALINHA)
		 {
		 	 *tamanhoCampo = (*tamanhoCampo)*10;
		 	 *tamanhoCampo = *tamanhoCampo + (int)c - CONVERSAOINTEIRO;
		 	 c = getc(config);
		 }
	fclose(config);
}

void criaArquivosMenores(char *entrada, int comecoCampo, int tamanhoCampo, int memoria)
{
	FILE *f, *Entrada, *listaArquivos;
	VetorRegistros vetor = NULL;
	int memoriaDisponivel, aux = 0, i = 0, j = 0, numeroRegistros, registroAtual = 0;
	char aux2[14], c;

	memoriaDisponivel = memoria - 3*sizeof(FILE*) - sizeof(VetorRegistros) - 7*sizeof(int) - 15*sizeof(char);
	//desconta da memoria disponivel as variaveis declaradas no inicio desta funcao

	numeroRegistros = memoriaDisponivel / (sizeof(Registro) + tamanhoCampo+1);
	//numero de structs "Registro" que poderemos ter no espaco que sobrou

	Entrada = fopen(entrada, "r");
	//abre arquivo de dados
	listaArquivos = fopen(LISTAARQUIVOSMENORES, "w+");
	//cria arquivo que guardara lista de arquivos menores criados nesta funcao

	vetor = (VetorRegistros) Malloc(numeroRegistros * sizeof(Registro));
	//aloca vetor de structs "Registro"

	fseek(Entrada, comecoCampo - 1 , SEEK_SET);
	//coloca entrada no comeco do campo do primeiro registro

	while(1) //enquanto o arquivo de entrada nao terminar
	{
		aux++; //aux guardara numero de arquivos menores criados
		fprintf(listaArquivos, "a%08d.txt%c", aux, QUEBRALINHA);
		fseek(listaArquivos, 14*(aux-1) ,SEEK_SET);

		i = 0;
		while (i < 14) //aux2 guarda nome do arquivo menor de chaves corrente
		{
			aux2[i] = getc(listaArquivos);
			i++;
		}
		aux2[0] = 't'; //"t" de temporario, pois ainda sera ordenado
		aux2[i-1] = FINALSTRING;
		i = 0;

		while (i < numeroRegistros)
		/*enquanto nao completa vetor de structs ou nao chega
		 ao fim do arquivo de entrada*/
		{
			vetor[i].info = (char*)Malloc(tamanhoCampo+1);
			//aloca string para ler o campo escolhido do atual registro
			vetor[i].end_fisico = registroAtual * TAMANHOREGISTRO;
			//grava posicao fisica deste registro
			j = 0;
			while (j < tamanhoCampo)
			{
				vetor[i].info[j] = getc(Entrada);
				j++;
			}
			vetor[i].info[j] = FINALSTRING;

			fseek(Entrada, TAMANHOREGISTRO - tamanhoCampo, SEEK_CUR);
			//atualiza entrada para o proximo registro do arquivo de dados

			i++; //i grava a posicao no vetor de structs
			registroAtual++; //registroAtual grava quantidade de registros lidos

			c = getc(Entrada);
			if(feof(Entrada)) break;
			else ungetc(c, Entrada);
		}

		f = fopen(aux2, "w"); //abre arquivo de chaves
		for (j = 0; j < i; j++) //copia as informacoes obtidas pelo vetor de structs
		{
			fprintf(f, "%s", vetor[j].info);
			fprintf(f, "%c%.8d\n", CARACTERSEPARADOR, vetor[j].end_fisico);
			free(vetor[j].info);
		}
		fclose(f);
		c = getc(Entrada);
		if (feof(Entrada)) break;
		else ungetc(c, Entrada);
	}
	free(vetor);
	fclose(Entrada);
	fclose(listaArquivos);
}

void ordenaArquivosMenores()
{
	FILE *listaArquivos;
	char string[] = "sort -o a00000000.txt t00000000.txt", string2[] = "rm t00000000.txt", nomeArquivo[14];
	int i = 0;

	listaArquivos = fopen(LISTAARQUIVOSMENORES, "r");


	fscanf(listaArquivos, "%s", &nomeArquivo);

	while(!feof(listaArquivos))
	{
		for(i=0;i<13;i++)
			string[i+8] = nomeArquivo[i];
		for(i=0;i<13;i++)
		{
			string[i+22] = nomeArquivo[i];
			string2[i+3] = nomeArquivo[i];
		}
		string[22] = 't';
		string2[3] = 't';
		system(string);
		system(string2);
		fscanf(listaArquivos, "%s", &nomeArquivo);
	}
	fclose(listaArquivos);
}

ListaArq encontrarMenorValor(ListaArq lista){
/*dada uma lista devolve o menor valor*/
	ListaArq menor;
	menor=NULL;

	if(lista->proximo == NULL){ /*Caso em que tem somente Um registro*/
		menor=lista;
		return(menor);
	}

	while(lista->proximo!=NULL){ /*Enquanto não final de lista*/
		if ((lista->tipo.chaveRa) < (((lista->proximo)->tipo.chaveRa))){
				menor=lista;
				lista=lista->proximo; //Caminho na lista
		}
		else{
				lista=lista->proximo;
		}

	}
	if(menor==NULL){ /* Caso em que o ultimo é o menor elemento*/
		menor=lista;
	}
	else{
		if(menor->tipo.chaveRa > lista->tipo.chaveRa){ /* Caso em que o ultimo é o menor elemento*/
			menor=lista;
		}
	}
	return(menor);
}

void liberarLista(ListaArq lista){

	ListaArq aux;

	lista=lista->proximo;
	while(lista->proximo != NULL)
	{
		aux=lista->proximo;
		free(lista);
		lista=aux;

	}
}

void inicializaLista(int i, ListaArq *q, inicio vetorDePonteirosParaArquivos)
/* Inicializa a lista com os primeiros registros de cada aquivo*/
{
	int ra, ind;
	char caracterseparador;

	/*Ler os registros*/
	fscanf(vetorDePonteirosParaArquivos[i], "%d", &ra);
	fscanf(vetorDePonteirosParaArquivos[i], "%c", &caracterseparador);
	fscanf(vetorDePonteirosParaArquivos[i], "%d", &ind);

	/*inicializar a lista*/
	(*q)->tipo.chaveRa=ra;
	(*q)->indice=ind;
	(*q)->i=i;
	(*q)->proximo=NULL;

	while(i >= 0)/*enquanto houver aquivos inicializao primeiros valores*/
	{
		fscanf(vetorDePonteirosParaArquivos[i], "%d", &ra);
		fscanf(vetorDePonteirosParaArquivos[i], "%c", &caracterseparador);
		fscanf(vetorDePonteirosParaArquivos[i], "%d", &ind);

		ListaArq p = (ListaArq) Malloc(sizeof(Arquivo));

		p->proximo=(*q)->proximo;
		(*q)->proximo=p;
		p->tipo.chaveRa=ra;
		p->indice=ind;
		p->i=i;
		(*q)=(*q)->proximo;
		i--;
	}


}



void mergeSortRa(int memoria, char *saida, int tamanhoCampo)
/*realiza o mergeSort*/
{
	FILE *listaArquivos, *arquivoSaida;
	inicio vetorDePonteirosParaArquivos;
	ListaArq lista,menor;
	char nomeArquivo[14], string[]="arq01.txt", caracterseparador;
	int ra,ind,flag=0,cont=0;


	/*desconta a memoria utilizada na criacao de variaveis no inicio da funcao*/
	int numeroArquivos, i, memoriaDisponivel = memoria - 2*sizeof(FILE*) - sizeof(inicio) - sizeof(ListaArq) - 2*sizeof(int) - (34+2*tamanhoCampo)*sizeof(char) - 2*sizeof(char*);

	/*calcula quantos arquivos poderao ser abertos simultaneamente com a memoria disponivel*/
	numeroArquivos = memoriaDisponivel / (sizeof(FILE*)+sizeof(Arquivo));

	/*aloca um vetor de ponteiros para os arquivos a serem abertos*/
	vetorDePonteirosParaArquivos = (inicio) Malloc(numeroArquivos * sizeof(FILE*));

	/*abre arquivos da lista dos arquivos e saida*/
	listaArquivos = fopen(LISTAARQUIVOSMENORES, "r");

	arquivoSaida=fopen(string,"w+");

	do
	{

		cont++;//contador para modificar o nome do arq01.txt

		/*percorre o vetor abrindo um arquivo em cada posicao*/
		i=0;
		do{
			fscanf(listaArquivos, "%s", &nomeArquivo);
			vetorDePonteirosParaArquivos[i] = fopen(nomeArquivo, "r");
			i++;
		}while(i<numeroArquivos && !feof(listaArquivos) );


		i--; //desconta ultima posicao
		/*verifica se nao e o caso em que esgotou o numero de arquivos*/
		if(!feof(listaArquivos)){
				(vetorDePonteirosParaArquivos[i]=fopen(string,"r"));
				string[4]=cont;
				arquivoSaida=fopen(string,"w+");
		}
		else{ /*senao gerar o arquivo de saida final*/
				arquivoSaida=fopen(saida,"w+");
				(vetorDePonteirosParaArquivos[i]=fopen(string,"r"));
		}


		ListaArq q = (ListaArq) Malloc(sizeof(Arquivo));
		q->tipo.chaveRa=0;
		lista =q;

		numeroArquivos=i; //guardo o numero de aquivos

		inicializaLista(i,&(q),vetorDePonteirosParaArquivos); //inicializo a lista

		//i=numeroArquivos;//retorno o valor de numero de arquivos
		ListaArq aux = lista; //guardo o apontador para inicio da lista
		ListaArq aux2;

		/*recebe o menor valor da lista*/
		menor=encontrarMenorValor(lista);

		/*coloco no arquivo de saida*/
		fprintf(arquivoSaida, "%.6d", (menor->tipo.chaveRa));
		fprintf(arquivoSaida, "%c", CARACTERSEPARADOR);
		fprintf(arquivoSaida, "%.8d\n", menor->indice);


		do
		{
				/*ler novamente o arquivo de entrada, agora somente na lista em que deve-se caminhar*/
				fscanf(vetorDePonteirosParaArquivos[menor->i], "%d", &ra);
				fscanf(vetorDePonteirosParaArquivos[menor->i], "%c", &caracterseparador);
				fscanf(vetorDePonteirosParaArquivos[menor->i], "%d", &ind);

				if(ra==(menor->tipo.chaveRa)){/*caso fim de arquivo com uma linha a mais no final*/
					menor->tipo.chaveRa=999999; /*high value*/
				}
				else{
					menor->tipo.chaveRa=ra; /*atualizo valores da registro*/
					menor->indice=ind;
				}

				lista=aux;/*retorno do valor inicial da lista*/

				/*recebe o menor valor da lista*/
				menor=encontrarMenorValor(lista);

				/*grava no arquivo de saida*/
				fprintf(arquivoSaida, "%.6d", (menor->tipo.chaveRa));
				fprintf(arquivoSaida, "%c", CARACTERSEPARADOR);
				fprintf(arquivoSaida, "%.8d\n", menor->indice);

				i=0;
				do{
					flag=0;
					/*verifica se todos os arquivos terminaram*/
					if(!feof(vetorDePonteirosParaArquivos[i]) || !feof(vetorDePonteirosParaArquivos[menor->i])){
						flag=1;
						break;
					}
					else{
						ListaArq aux2;
						aux=lista;
						if(lista==menor){
							lista=menor->proximo;
							aux=lista;
						}

						else
						{
							while((lista->proximo)!=menor ){
							lista=lista->proximo;
							}

							lista->proximo=menor->proximo;
						}
						aux2=menor;
						free(aux2);
						if(lista==NULL){
							flag=0;
							lista=aux;
							break;
						}
						menor=lista;

					}
					i++;
				}while(i!=numeroArquivos);

			}while(flag!=0);


			fclose(arquivoSaida);
	}while(!(feof(listaArquivos)));






	/*fecha os arquivos da lista dos arquivos e saida*/
	fclose(listaArquivos);
	//fclose(arquivoSaida);
}

int main (int argc, char *argv[])
{
	int comecoCampo = 0, tamanhoCampo = 0, memoria = 0;
	char *entrada, *saida, *campo, *tamanhoMemoria,*campoNome;

	entrada = argv[1];
	saida = argv[2];
	campo = argv[3];
	tamanhoMemoria = argv[4];

	verificaErros(argc, entrada, campo, tamanhoMemoria, &memoria);

	carregaConfiguracoes(&comecoCampo, &tamanhoCampo, campo);

	criaArquivosMenores(entrada, comecoCampo, tamanhoCampo, memoria);

	ordenaArquivosMenores();
	campoNome="NOME";


	mergeSortRa(memoria, saida, tamanhoCampo);




	return 0;
}
